package bitmap;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

import colour.Colour;
/**
 * This class for bitmap read and write
 * @author Administrator
 *
 */
public class BitmapIO implements IBitmapIO {

	/**
	 * This method reads the PPM file into the system whihc ignores the **
	 * comments and whiteplaces.In the color compont part, call the ** different
	 * method from BitmapInP3 or BitmapInP6 to deal with ** the read and write
	 * **
	 * 
	 * @param path
	 *            the file location of the PPM file
	 */
	public Bitmap readBitmap(final String path) {
		// data for read
		// give a defalt method for read
		String magicNumber = "P6";
		int width = -1;
		int height = -1;
		int maxColourValue = 255;
		Colour[][] pixels = new Colour[1][1];
		Bitmap bpRead;

		int readData;
		int flag = 4;
		try {
			// open the stream
			File file1 = new File(path);
			if (!file1.exists()) {
				System.out
						.println("fail to open file: "+path+" does not exist");

			} else {

				FileInputStream fis = new FileInputStream(path);

				while ((readData = fis.read()) != -1) {
					// find "#"
					if (readData == 35) {
						// until the end of the line
						while ((readData = fis.read()) != 10)
							;
					}
					// to ignore the whitespace
					if ((readData == 10) || (readData == 13)
							|| (readData == 32) || (readData == 9)) {
						continue;
					} else {
						if (flag == 4) {
							magicNumber = getMagicNumber(fis, readData);
							flag--;
						} else if (flag == 3) {
							width = this.getValueToInt(fis, readData);
							flag--;
						} else if (flag == 2) {
							height = this.getValueToInt(fis, readData);
							flag--;
						} else if (flag == 1) {
							maxColourValue = this.getValueToInt(fis, readData);
							flag--;
						}
						if (flag == 0) {
							if ("P3".equals(magicNumber)) {
								BitmapInP3 bip3 = new BitmapInP3(width, height,
										maxColourValue);
								pixels = bip3.readColour(fis, readData);
								break;

							} else {
								BitmapInP6 bip6 = new BitmapInP6(width, height,
										maxColourValue);
								pixels = bip6.readColour(fis, readData);
								break;
							}
						}
					}
				}

				// close the stream
				fis.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		bpRead = new Bitmap(magicNumber, width, height, maxColourValue, pixels);
		return bpRead;
	}

	/**
	 * 
	 * Write the content of bitmap including the magicNumber,comment,width and
	 * height of the image,maximal value of the colour component and value of
	 * each colour component. Two format named "P3" and "P6" are used."P3"
	 * stores the information fis ASCII and "P6" storesin binary format
	 * 
	 * 
	 * @param Path
	 *            the path of the PPM file
	 * @return the value shows whether it is successful for writing
	 * 
	 */

	public boolean writeBitmap(final String Path, Bitmap bpWrite) {

		String magicNumber = bpWrite.getMagicNumber();
		int maxColourValue = bpWrite.getMaxColourValue();

		int width = bpWrite.getWidth();
		int height = bpWrite.getHeight();

		try {

			// make sure that the magicNumber is valid number

			if ("P6".equals(magicNumber) || "P3".equals(magicNumber)) {

				File file = new File(Path);
				if (!file.exists()) {
					file.createNewFile();

				} 

				FileOutputStream fos = new FileOutputStream(Path);

				PrintStream pStream = new PrintStream(fos);

				// write the way of storage into PPM
				if (!writeType(pStream, magicNumber, "\n")) {
					System.out
							.println("Magic number is not written correctly!");
					return false;
				}

				// write some comments
				if (!writeComment(pStream, "# Ray Tracer")) {
					System.out.println("Comment is not written correctly!");
					return false;
				}

				// write the width and height of the image into PPM
				if (!writeHeightandWidth(pStream, width, height, " ", "\n")) {
					System.out
							.println("Height and width are not written correctly!");
					return false;
				}

				// write the maximal colour value should be handled
				if (!writeMaxColourValue(pStream, maxColourValue, "\n")) {
					System.out
							.println("Maximal colour value is not written correctly!");
					return false;
				}

				if (!writeColour(fos, bpWrite, "\t")) {
					System.out
							.println("Colour components are not written correctly!");
					return false;
				}
				pStream.close();
				fos.close();

			} else {
				System.out.println("magic number is valid!");
				return false;
			}

		} catch (Exception e) {
			System.out.println("write error fis PPM");
			return false;
		}
		return true;
	}

	/**
	 * This is a method for writing colour component into files. Call the method
	 * of writing colour component depending on the magic number.
	 * 
	 * @param fos
	 *            The stream is created outside for writing
	 * @param magicNumber
	 *            The string is used for identify the way of writing. P3: fis
	 *            ASCII or P6 fis binary
	 * @param whitespace
	 *            used only if magicNuber is "P3"
	 * 
	 * @return a type of boolean shows whether the operation is sucessful
	 * 
	 */

	private boolean writeColour(FileOutputStream fos, final Bitmap bpWrite,
			final String whitespace) {
		if ("P3".equals(bpWrite.getMagicNumber())) {

			BitmapInP3 bip3 = new BitmapInP3(bpWrite);

			// write the colour group into file
			if (!bip3.writeColour(fos, whitespace)) {
				return false;
			}
		} else if ("P6".equals(bpWrite.getMagicNumber())) {
			BitmapInP6 bip6 = new BitmapInP6(bpWrite);

			if (!bip6.writeColour(fos, whitespace)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * This is a method for writing magicNumber into files.
	 * 
	 * @param ps
	 *            The stream is created outside for writing
	 * @param type
	 *            The string is used for writing into files
	 * @param whitespace
	 *            used for dividing the content
	 * 
	 * @return a type of boolean shows whether the operation is sucessful
	 * 
	 */
	private boolean writeType(PrintStream pStream, String type,
			String whitespace) {
		if (validWhitespace(whitespace)) {
			pStream.print(type + whitespace);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * This is a method for writing width and height of the image into files.
	 * 
	 * @param ps
	 *            The stream is created outside for writing
	 * @param width
	 *            The string is used for writing into files,shows the width of
	 *            the image
	 * @param height
	 *            The string is used for writing into files,shows the height of
	 *            the image
	 * 
	 * @param whitespaceW
	 *            used for dividing the content after width
	 * @param whitespaceH
	 *            used for dividing the content after height
	 * 
	 * @return a type of boolean shows whether the operation is sucessful
	 * 
	 */
	private boolean writeHeightandWidth(PrintStream pStream, int width,
			int height, String whitespaceW, String whitespaceH) {
		if (validWhitespace(whitespaceW) && validWhitespace(whitespaceH)) {
			pStream.print(width + whitespaceW + height + whitespaceH);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * This is a method for writing comment into files.
	 * 
	 * @param ps
	 *            The stream is created outside for writing
	 * @param comment
	 *            The string is used for writing into files,shows the comment
	 *            for the image
	 * 
	 * 
	 * @return a type of boolean shows whether the operation is sucessful
	 * 
	 */
	private boolean writeComment(PrintStream pStream, String comment) {
		pStream.println(comment);
		return true;
	}

	/**
	 * This is a method for writing Maximal Colour Value into files.
	 * 
	 * @param ps
	 *            The stream is created outside for writing
	 * @param mValue
	 *            The integeris used for writing into files,shows maximal value
	 *            of the colour component
	 * @param whitespace
	 *            used for dividing the content *
	 * 
	 * @return a type of boolean shows whether the operation is sucessful
	 * 
	 */
	private boolean writeMaxColourValue(PrintStream pStream, int mValue,
			String whitespace) {
		if (validWhitespace(whitespace)) {
			pStream.print(mValue + whitespace);
			return true;
		} else {
			return false;
		}
	}

	private boolean validWhitespace(String whitespace) {
		return " ".equals(whitespace) || "\t".equals(whitespace)
				|| "\n".equals(whitespace) || "\r".equals(whitespace);

	}

	/**
	 * This is a method for get magic number from the file
	 * 
	 * @param fis
	 *            a FileInputStream
	 * @param charValue
	 *            the first character to be compare
	 * @return magic number
	 * @see String
	 * 
	 */
	private String getMagicNumber(FileInputStream fis, final int charValue)
			throws IOException {
		String magicNumber;
		int charV = charValue;
		StringBuffer tempString = new StringBuffer(Character
				.toString((char) charV));

		charV = fis.read();
		tempString.append((char) charV);

		if ("P3".equals(tempString.toString())) {
			magicNumber = "P3";
		} else {
			magicNumber = "P6";
		}
		return magicNumber;
	}

	/**
	 * This is a method for get value from the file and transform it into
	 * interger. Stop before the whitespace
	 * 
	 * @param fis
	 *            a FileInputStream
	 * @param charValue
	 *            the first character should be used
	 * 
	 * @return value from the file
	 */
	private int getValueToInt(FileInputStream fis, int charValue)
			throws IOException {
		int charV = charValue;
		StringBuffer tempString = new StringBuffer(Character
				.toString((char) charV));

		while ((charV = fis.read()) != -1) {
			// check the whitespace
			if ((charV == 10) || (charV == 13) || (charV == 32) || (charV == 9)) {
				break;
			} else {
				tempString.append((char) charV);
			}
		}
		return Integer.parseInt(tempString.toString());
	}

}
